﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using GLWrap;
using GLWrap.SceneObjects;
using GLWrap.SceneObjects.Line;
using ReAnimation.Domain;
using ReAnimation.Domain.Policies.Interfaces;
using ReAnimation.Helpers.Common;
using ReAnimation.Helpers.WinForms;
using Sharp3D.Math.Core;
using ReAnimation.Helpers.DynamicProxy;

namespace ReAnimation.Graphics
{
    //TODO: use only through interface
    public class MoleculeDrawer : IMoleculeDrawer, IDisposable
    {
        public Color SelectedAtomColor
        {
            get
            { return _selectedAtomColor; }
            set
            {
                if (_selectedAtomColor.Equals(value))
                    return;

                _selectedAtomColor = value;
                _selectedMaterial = _materialFactory.CreateMaterial(value);
            }
        }

        public IGlSceneManager GlSceneManager { get; private set; }
        public Molecule Molecule
        {
            get
            {
                return _molecule;
            }
            set
            {
                if (_molecule != null && _molecule.Equals(value))
                    return;
                if (_molecule != null) 
                EventsSource.EditSubscriptionsFor(_molecule).RemoveHandler(_action);

                _molecule = value;
                if(_molecule!=null)
                EventsSource.EditSubscriptionsFor(_molecule).AfterPropertySet(() => _molecule.TransformMatrix)
                    .Handler(_action);

                SynchronizeMoleculeWithModel();
            }
        }

        private Color _linkColor = Color.Yellow;
        public Color LinkColor
        {
            get
            {
                return _linkColor;
            }
            set
            {
                _linkColor = value;
                if(_molecule!=null)
                SynchronizeMoleculeWithModel();
            }
        }

        public SceneObject MoleculeSceneObject
        {
            get { return _moleculeSceneObjectsGroup; }
        }

        public IDictionary<SceneObject, Atom> SceneToAtomMap
        {
            get { return new Dictionary<SceneObject, Atom>(_sceneToAtomMapping); }
        }

        public IDictionary<SceneObject, Link> SceneToLinkMap
        {
            get { return new Dictionary<SceneObject, Link>(_sceneToLinkMapping); }
        }

        public IDictionary<Atom, AtomEmphasizeParams> EmphasizedAtoms
        {
            get { return new Dictionary<Atom, AtomEmphasizeParams>(_emphasizedAtoms); }
        }

        public void EmphasizeAtom(Atom atom, AtomEmphasizeParams atomEmphasizeParams)
        {
            SceneObject sceneObject = GetSceneObjectByAtom(atom);
            if (sceneObject == null) return;
            sceneObject.Material = _materialFactory.CreateMaterial(atomEmphasizeParams.Color);
            GlSceneManager.Refresh();
        }

        private SceneObject GetSceneObjectByAtom(Atom atom)
        {
            var selectedObjects = from pair in _sceneToAtomMapping
                                  where pair.Value == atom
                                  select pair.Key;

            return selectedObjects.FirstOrDefault();
        }

        public void DeemphasizeAtom(Atom atom)
        {
            SceneObject sceneObject = GetSceneObjectByAtom(atom);
            if (sceneObject == null) return;
            sceneObject.Material = _materialFactory.CreateMaterialForAtom(atom);
            GlSceneManager.Refresh();
        }

        public void EmphasizeAtoms(IDictionary<Atom, AtomEmphasizeParams> atomSelections)
        {
            foreach (var paramse in atomSelections)
                EmphasizeAtom(paramse.Key, paramse.Value);
        }

        public void DeemphasizeAtoms(IEnumerable<Atom> atoms)
        {
            foreach (var atom in atoms)
                DeemphasizeAtom(atom);
        }

        public event EventHandler<EventArg<Atom>> AtomSelected;

        private MaterialFactory _materialFactory;
        private Material _selectedMaterial;
        private Material _normalLinkMaterial;
        private Color _selectedAtomColor;
        private IDictionary<SceneObject, Atom> _sceneToAtomMapping;
        private IDictionary<SceneObject, Link> _sceneToLinkMapping;
        private IDictionary<Atom, AtomEmphasizeParams> _emphasizedAtoms;
        private ILinkSceneObjectsFactory _linkSceneObjectsFactory;
        private IVisualizationPolicy _visualizationPolicy;
        private SceneObjectsGroup _moleculeSceneObjectsGroup;
        
        private Molecule _molecule;
        private ReAnimation.Helpers.DynamicProxy.Action _action; 
        public MoleculeDrawer(IVisualizationPolicy visualizationPolicy,ILinkSceneObjectsFactory linkSceneObjectsFactory)
        {
            _action = () => 
            { 
                _moleculeSceneObjectsGroup.TransformationMatrix = _molecule.TransformMatrix; 
            };
            _materialFactory = new MaterialFactory();
            _visualizationPolicy = visualizationPolicy;
            _linkSceneObjectsFactory = linkSceneObjectsFactory;
            _sceneToAtomMapping = new Dictionary<SceneObject, Atom>();
            _sceneToLinkMapping = new Dictionary<SceneObject, Link>();
            _emphasizedAtoms = new Dictionary<Atom, AtomEmphasizeParams>();
            _moleculeSceneObjectsGroup = new SceneObjectsGroup();


            SelectedAtomColor = Color.Blue;

            _normalLinkMaterial = _materialFactory.CreateMaterial(Color.Green);
            _selectedMaterial = _materialFactory.CreateMaterial(SelectedAtomColor, 85);
            _linkSceneObjectsFactory.DefaultMaterial = _normalLinkMaterial;
        }

        public MoleculeDrawer(IVisualizationPolicy visualizationPolicy):this(visualizationPolicy,new LinkSceneObjectsFactory())
        {}

        private void SynchronizeMoleculeWithModel()
        {
            
            RemoveUnusedObjects();
            AddNewObjects();
            ReassignPositions();
            _moleculeSceneObjectsGroup.TransformationMatrix = _molecule.TransformMatrix;
        }

        private void ReassignPositions()
        {
            foreach (var sceneToAtom in SceneToAtomMap)
            {
                (sceneToAtom.Key as Sphere).Position = ConvertPositionToOpenGLCoords(sceneToAtom.Value.Position);
                (sceneToAtom.Key as Sphere).Radius = _visualizationPolicy.ConvertAngstromToLocal(sceneToAtom.Value.AtomDescription.Radius)/2;
                (sceneToAtom.Key as Sphere).Material = _materialFactory.CreateMaterialForAtom(sceneToAtom.Value);
            }
            foreach(var sceneToLink in SceneToLinkMap)
            {
                (sceneToLink.Key as BaseLine).PointA = ConvertPositionToOpenGLCoords(sceneToLink.Value.Atom1.Position);
                (sceneToLink.Key as BaseLine).PointB = ConvertPositionToOpenGLCoords(sceneToLink.Value.Atom2.Position);
                (sceneToLink.Key as BaseLine).Material = _materialFactory.CreateMaterial(LinkColor);
            }
        }

        private void AddNewObjects()
        {
            var newAtoms = from a in _molecule.Atoms
                           where !_sceneToAtomMapping.Values.Contains(a)
                           select a;
            var newLinks = from link in _molecule.Links
                           where !_sceneToLinkMapping.Values.Contains(link) 
                                 &&link.LinkType != MoleculeLinkType.None
                           select link;

            AddAtomsToScene(newAtoms);
            AddLinksToScene(newLinks);
        }

        private void AddLinksToScene(IEnumerable<Link> newLinks)
        {
            foreach (var link in newLinks)
            {
                SceneObject linkSceneObject = _linkSceneObjectsFactory.GetSceneObjectForLinkType(link);
                linkSceneObject.Material = _materialFactory.CreateMaterial(LinkColor);
                _moleculeSceneObjectsGroup.AttachObject(linkSceneObject);

                _sceneToLinkMapping.Add(linkSceneObject, link);
            }
        }

        private void AddAtomsToScene(IEnumerable<Atom> newAtoms)
        {
            foreach (var atom in newAtoms)
            {
                Sphere sceneObject = new Sphere
                                            {
                                                Material = _materialFactory.CreateMaterialForAtom(atom),

                                                Position = ConvertPositionToOpenGLCoords(atom.Position),
                                                                
                                                Radius = _visualizationPolicy.ConvertAngstromToLocal(atom.AtomDescription.Radius)/2
                                            };
                
                _moleculeSceneObjectsGroup.AttachObject(sceneObject);
                _sceneToAtomMapping.Add(sceneObject, atom);
            }
        }

        private Vector3F ConvertPositionToOpenGLCoords(Vector3F position)
        {
            return new Vector3F(_visualizationPolicy.ConvertAngstromToLocal(position.X),
                                _visualizationPolicy.ConvertAngstromToLocal(position.Y),
                                _visualizationPolicy.ConvertAngstromToLocal(position.Z));
        }

        private void RemoveUnusedObjects()
        {
            var unusedSceneAtoms = (from objectToAtomMap in _sceneToAtomMapping
                                    where !_molecule.Atoms.Contains(objectToAtomMap.Value)
                                    select objectToAtomMap.Key).ToArray();

            var unusedSceneLinks = (from objectToLinkMap in _sceneToLinkMapping
                                    where 
                                    !_molecule.Links.Contains(objectToLinkMap.Value)
                                    || objectToLinkMap.Value.LinkType == MoleculeLinkType.None
                                    || !_linkSceneObjectsFactory.CheckSceneObjectIsLinkType(objectToLinkMap.Value.LinkType,objectToLinkMap.Key)
                                    select objectToLinkMap.Key).ToArray();


            unusedSceneAtoms.ForEach(o =>
                                         {
                                             _sceneToAtomMapping.Remove(o);
                                             _moleculeSceneObjectsGroup.DetachObject(o);
                                             
                                         });

            unusedSceneLinks.ForEach(o =>
                                         {
                                             _sceneToLinkMapping.Remove(o);
                                             _moleculeSceneObjectsGroup.DetachObject(o);
                                         });
        }

        public void Init(IGlSceneManager glSceneManager)
        {
            GlSceneManager = glSceneManager;
            glSceneManager.AddSceneObject(_moleculeSceneObjectsGroup);
        }

        public void Update(bool withRefresh)
        {
            SynchronizeMoleculeWithModel();
            
            if(withRefresh) GlSceneManager.Refresh();
        }

        public void Dispose()
        {//TODO: mark disposed
            GlSceneManager.RemoveSceneObject(_moleculeSceneObjectsGroup);
        }
    }
}